use bitflags::bitflags;
use ffi::*;

/// MDBX sync mode
#[derive(Clone, Copy, Debug)]
pub enum SyncMode {
    /// Default robust and durable sync mode.
    /// Metadata is written and flushed to disk after a data is written and
    /// flushed, which guarantees the integrity of the database in the event
    /// of a crash at any time.
    Durable,

    /// Don't sync the meta-page after commit.
    ///
    /// Flush system buffers to disk only once per transaction commit, omit the
    /// metadata flush. Defer that until the system flushes files to disk,
    /// or next non-read-only commit or
    /// [Environment::sync()](crate::Environment::sync). Depending on the
    /// platform and hardware, with [SyncMode::NoMetaSync] you may get a
    /// doubling of write performance.
    ///
    /// This trade-off maintains database integrity, but a system crash may undo
    /// the last committed transaction. I.e. it preserves the ACPI
    /// (atomicity, consistency, isolation) but not D (durability) database
    /// property.
    NoMetaSync,

    /// Don't sync anything but keep previous steady commits.
    ///
    /// [SyncMode::UtterlyNoSync] the [SyncMode::SafeNoSync] flag disable
    /// similarly flush system buffers to disk when committing a
    /// transaction. But there is a huge difference in how are recycled the
    /// MVCC static_files corresponding to previous "steady" transactions (see
    /// below).
    ///
    /// With [crate::EnvironmentKind::WriteMap] the [SyncMode::SafeNoSync]
    /// instructs MDBX to use asynchronous mmap-flushes to disk.
    /// Asynchronous mmap-flushes means that actually all writes will
    /// scheduled and performed by operation system on it own manner, i.e.
    /// unordered. MDBX itself just notify operating system that it would be
    /// nice to write data to disk, but no more.
    ///
    /// Depending on the platform and hardware, with [SyncMode::SafeNoSync] you
    /// may get a multiple increase of write performance, even 10 times or
    /// more.
    ///
    /// In contrast to [SyncMode::UtterlyNoSync] mode, with
    /// [SyncMode::SafeNoSync] flag MDBX will keeps untouched pages within
    /// B-tree of the last transaction "steady" which was synced to
    /// disk completely. This has big implications for both data durability and
    /// (unfortunately) performance:
    /// - A system crash can't corrupt the database, but you will lose the last
    ///   transactions; because MDBX will rollback to last steady commit since
    ///   it kept explicitly.
    /// - The last steady transaction makes an effect similar to "long-lived"
    ///   read transaction since prevents reuse of pages freed by newer write
    ///   transactions, thus the any data changes will be placed in newly
    ///   allocated pages.
    /// - To avoid rapid database growth, the system will sync data and issue a
    ///   steady commit-point to resume reuse pages, each time there is
    ///   insufficient space and before increasing the size of the file on disk.
    ///
    /// In other words, with
    /// [SyncMode::SafeNoSync] flag MDBX protects you from the whole database
    /// corruption, at the cost increasing database size and/or number of
    /// disk IOPs. So, [SyncMode::SafeNoSync] flag could be used with
    /// [Environment::sync()](crate::Environment::sync) as alternatively for
    /// batch committing or nested transaction (in some cases).
    ///
    /// The number and volume of of disk IOPs with [SyncMode::SafeNoSync] flag
    /// will exactly the as without any no-sync flags. However, you should
    /// expect a larger process's work set and significantly worse a
    /// locality of reference, due to the more intensive allocation of
    /// previously unused pages and increase the size of the database.
    SafeNoSync,

    /// Don't sync anything and wipe previous steady commits.
    ///
    /// Don't flush system buffers to disk when committing a transaction.
    /// This optimization means a system crash can corrupt the database, if
    /// buffers are not yet flushed to disk. Depending on the platform and
    /// hardware, with [SyncMode::UtterlyNoSync] you may get a multiple
    /// increase of write performance, even 100 times or more.
    ///
    /// If the filesystem preserves write order (which is rare and never
    /// provided unless explicitly noted) and the
    /// [WriteMap](crate::EnvironmentKind::WriteMap) and
    /// [EnvironmentFlags::liforeclaim] flags are not used, then a system crash
    /// can't corrupt the database, but you can lose the last transactions,
    /// if at least one buffer is not yet flushed to disk. The risk is
    /// governed by how often the system flushes dirty buffers to
    /// disk and how often [Environment::sync()](crate::Environment::sync) is
    /// called. So, transactions exhibit ACPI (atomicity, consistency,
    /// isolation) properties and only lose D (durability). I.e. database
    /// integrity is maintained, but a system crash may undo the
    /// final transactions.
    ///
    /// Otherwise, if the filesystem not preserves write order (which is
    /// typically) or [WriteMap](crate::EnvironmentKind::WriteMap) or
    /// [EnvironmentFlags::liforeclaim] flags are used, you should expect
    /// the corrupted database after a system crash.
    ///
    /// So, most important thing about [SyncMode::UtterlyNoSync]:
    /// - A system crash immediately after commit the write transaction high
    ///   likely lead to database corruption.
    /// - Successful completion of
    ///   [Environment::sync(force=true)](crate::Environment::sync) after one or
    ///   more committed transactions guarantees consistency and durability.
    /// - BUT by committing two or more transactions you back database into a
    ///   weak state, in which a system crash may lead to database corruption!
    ///   In case single transaction after
    ///   [Environment::sync()](crate::Environment::sync), you may lose
    ///   transaction itself, but not a whole database.
    ///
    /// Nevertheless, [SyncMode::UtterlyNoSync] provides "weak" durability in
    /// case of an application crash (but no durability on system failure), and
    /// therefore may be very useful in scenarios where data durability is
    /// not required over a system failure (e.g for short-lived data), or if
    /// you can take such risk.
    UtterlyNoSync,
}

impl Default for SyncMode {
    fn default() -> Self {
        Self::Durable
    }
}

#[derive(Clone, Copy, Debug)]
pub enum Mode {
    ReadOnly,
    ReadWrite { sync_mode: SyncMode },
}

impl Default for Mode {
    fn default() -> Self {
        Self::ReadWrite { sync_mode: SyncMode::default() }
    }
}

impl From<Mode> for EnvironmentFlags {
    fn from(mode: Mode) -> Self {
        Self { mode, ..Default::default() }
    }
}

#[derive(Clone, Copy, Debug, Default)]
pub struct EnvironmentFlags {
    pub no_sub_dir:  bool,
    pub exclusive:   bool,
    /// Flag is intended to open an existing sub-database which was created with
    /// unknown flags In such cases, instead of returning the
    /// `MDBX_INCOMPATIBLE` error, the sub-database will be opened with
    /// flags which it was created, and then an application could determine the
    /// actual flags.
    pub accede:      bool,
    pub mode:        Mode,
    pub no_rdahead:  bool,
    pub no_meminit:  bool,
    pub coalesce:    bool,
    pub liforeclaim: bool,
}

impl EnvironmentFlags {
    /// Configures the mdbx flags to use when opening the environment.
    pub(crate) fn make_flags(&self) -> ffi::MDBX_env_flags_t {
        let mut flags = 0;

        if self.no_sub_dir {
            flags |= ffi::MDBX_NOSUBDIR;
        }

        if self.exclusive {
            flags |= ffi::MDBX_EXCLUSIVE;
        }

        if self.accede {
            flags |= ffi::MDBX_ACCEDE;
        }

        match self.mode {
            Mode::ReadOnly => {
                flags |= ffi::MDBX_RDONLY;
            }
            Mode::ReadWrite { sync_mode } => {
                flags |= match sync_mode {
                    SyncMode::Durable => ffi::MDBX_SYNC_DURABLE,
                    SyncMode::NoMetaSync => ffi::MDBX_NOMETASYNC,
                    SyncMode::SafeNoSync => ffi::MDBX_SAFE_NOSYNC,
                    SyncMode::UtterlyNoSync => ffi::MDBX_UTTERLY_NOSYNC,
                };
            }
        }

        if self.no_rdahead {
            flags |= ffi::MDBX_NORDAHEAD;
        }

        if self.no_meminit {
            flags |= ffi::MDBX_NOMEMINIT;
        }

        if self.coalesce {
            flags |= ffi::MDBX_COALESCE;
        }

        if self.liforeclaim {
            flags |= ffi::MDBX_LIFORECLAIM;
        }

        flags |= ffi::MDBX_NOTLS;

        flags
    }
}

bitflags! {
    #[doc="Database options."]
    #[derive(Default)]
    pub struct DatabaseFlags: MDBX_env_flags_t {
        const REVERSE_KEY = MDBX_REVERSEKEY;
        const DUP_SORT = MDBX_DUPSORT;
        const INTEGER_KEY = MDBX_INTEGERKEY;
        const DUP_FIXED = MDBX_DUPFIXED;
        const INTEGER_DUP = MDBX_INTEGERDUP;
        const REVERSE_DUP = MDBX_REVERSEDUP;
        const CREATE = MDBX_CREATE;
        const ACCEDE = MDBX_DB_ACCEDE;
    }
}

bitflags! {
    #[doc="Write options."]
    #[derive(Default)]
    pub struct WriteFlags: MDBX_env_flags_t {
        const UPSERT = MDBX_UPSERT;
        const NO_OVERWRITE = MDBX_NOOVERWRITE;
        const NO_DUP_DATA = MDBX_NODUPDATA;
        const CURRENT = MDBX_CURRENT;
        const ALLDUPS = MDBX_ALLDUPS;
        const RESERVE = MDBX_RESERVE;
        const APPEND = MDBX_APPEND;
        const APPEND_DUP = MDBX_APPENDDUP;
        const MULTIPLE = MDBX_MULTIPLE;
    }
}
